package com.usdt.web.controller.cp.salesTools;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.usdt.common.core.domain.R;
import com.usdt.system.domain.req.SalesDataExportReqVO;
import com.usdt.system.domain.resp.BatchClueRespVO;
import com.usdt.system.domain.resp.BatchVisitRespVO;
import com.usdt.system.domain.resp.ProjectScopRespVO;
import com.usdt.system.domain.resp.SaleUserDetailsExportRespVO;
import com.usdt.system.domain.resp.SaleUserDetailsRespVO;
import com.usdt.system.domain.resp.SalesCountRespVO;
import com.usdt.system.domain.resp.SalesDataRespVO;
import com.usdt.system.domain.resp.SignUserRespVo;
import com.usdt.system.dto.cp.ClueUserExcelDTO;
import com.usdt.system.dto.cp.SignSourceExcelDTO;
import com.usdt.system.dto.cp.SignUserExcelDTO;
import com.usdt.system.dto.cp.VisitUserExcelDTO;
import com.usdt.system.service.cp.SalesCountService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("/saleCount")
@Slf4j
@Tag(name = "销售统计")
public class SalesCountController {

	
	@Autowired
	private SalesCountService salesCountService;
	
    @GetMapping("/project")
    @Operation(summary ="项目筛选")
    public R<List<ProjectScopRespVO>> projectScop() {
    	
    	return R.ok(salesCountService.projectScop());
    }

    @GetMapping("/clue/user")
    @Operation(summary ="线索-业务员汇总")
    public R<SalesCountRespVO> getClueUserCount(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
    		) {
    	
    	return R.ok(salesCountService.getClueUserCount(startDate, endDate, deptIds));
    }
    
    @GetMapping("/clue/user/data")
    @Operation(summary ="线索-业务员汇总-下钻")
    public R<IPage<BatchClueRespVO>> getClueUserData(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
    		@Schema(description = "id")@RequestParam(value = "id", required = true)String id,
    		@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
    		@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
    		@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
    		) {
    	
    	return R.ok(salesCountService.getClueUserData(startDate, endDate, deptIds,id,pageNum,pageSize,conditions));
    }
    
    @PostMapping("/clue/user/data/export")
    @Operation(summary ="线索-业务员汇总-下钻-导出")
    public void getClueUserDataExport(
    		HttpServletResponse response,
    		@RequestBody @Valid SalesDataExportReqVO vo
    		) throws IOException {
    	List<ClueUserExcelDTO> list = salesCountService.getClueUserDataExport(
    			vo.getStartDate(),vo.getEndDate(), vo.getDeptIds(),
    			vo.getId(),vo.getPageNum(),vo.getPageSize(),
    			vo.getConditions());
    	exportActivityExcel(response,list,ClueUserExcelDTO.class,"业务员销售汇总-线索登记列表","线索登记数据");
    }
    
    public void exportActivityExcel(HttpServletResponse response, List dataList, Class clazz
    		,String excelName,String sheetName) throws IOException {
        // 1. 设置响应头（关键：避免中文乱码、指定文件类型）
    	response.addHeader("Pargam", "no-cache");
    	response.addHeader("Cache-Control", "no-cache");
    	response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        //response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // Excel 2007+格式
    	//response.setContentType("application/octet-stream;charset=UTF-8"); // Excel 2007+格式
        response.setCharacterEncoding("utf-8");
        // 文件名编码：使用URLEncoder避免中文乱码，替换“+”为“%20”（浏览器兼容）
        String fileName = URLEncoder.encode(excelName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName + ".xlsx");

        // 2. 使用EasyExcel写入数据（try-with-resources自动关闭ExcelWriter）
        try (var excelWriter = EasyExcel.write(response.getOutputStream(), clazz).build()) {
            // 3. 构建Sheet（指定工作表名称，如“活动信息”）
            var sheet = EasyExcel.writerSheet(sheetName).build();
            // 4. 写入数据（将实体类列表映射为Excel行）
            excelWriter.write(dataList, sheet);
        }
    }
    
    @GetMapping("/visit/user")
    @Operation(summary ="到访-业务员汇总")
    public R<SalesCountRespVO> getVisitUserCount(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
    		) {
    	
    	return R.ok(salesCountService.getVisitUserCount(startDate, endDate, deptIds));
    }
    
    @GetMapping("/visit/user/data")
    @Operation(summary ="到访-业务员汇总-下钻")
    public R<IPage<BatchVisitRespVO>> getVisitUserData(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
    		@Schema(description = "id")@RequestParam(value = "id", required = true)String id,
    		@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
    		@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
    		@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
    		) {
    	
    	return R.ok(salesCountService.getVisitUserData(startDate, endDate, deptIds,id,pageNum,pageSize,conditions));
    }
    
    @PostMapping("/visit/user/data/export")
    @Operation(summary ="到访-业务员汇总-下钻-导出")
    public void getVisitUserDataExport(
    		HttpServletResponse response,
    		@RequestBody @Valid SalesDataExportReqVO vo
    		) throws IOException {
    	
    	List<VisitUserExcelDTO> list = salesCountService.getVisitUserDataExport(
    			vo.getStartDate(), vo.getEndDate(), vo.getDeptIds(),
    			vo.getId(),vo.getPageNum(),vo.getPageSize(),
    			vo.getConditions());
    	exportActivityExcel(response,list,ClueUserExcelDTO.class,"业务员销售汇总-来访登记列表","来访访登记数据");
    }
    
    @GetMapping("/clue/source")
    @Operation(summary ="线索-渠道汇总")
    public R<SalesCountRespVO> getClueSourceCount(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
    		) {
    	
    	return R.ok(salesCountService.getClueSourceCount(startDate, endDate, deptIds));
    }
    
    @GetMapping("/clue/source/data")
    @Operation(summary ="线索-渠道汇总-下钻")
    public R<IPage<BatchClueRespVO>> getClueSourceData(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
    		@Schema(description = "id")@RequestParam(value = "id", required = true)String id,
    		@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
    		@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
    		@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
    		) {
    	
    	return R.ok(salesCountService.getClueSourceData(startDate, endDate, deptIds,id,pageNum,pageSize,conditions));
    }
    
    @PostMapping("/clue/source/data/export")
    @Operation(summary ="线索-渠道汇总-下钻-导出")
    public void getClueSourceDataExport(
    		HttpServletResponse response,
    		@RequestBody @Valid SalesDataExportReqVO vo
    		) throws IOException {
    	
    	List<ClueUserExcelDTO> list = salesCountService.getClueSourceDataExport(
    			vo.getStartDate(), vo.getEndDate(), vo.getDeptIds(),
    			vo.getId(),vo.getPageNum(),vo.getPageSize(),
    			vo.getConditions());
    	exportActivityExcel(response,list,ClueUserExcelDTO.class,"渠道销售汇总-线索登记列表","线索登记数据");
    }
    
    @GetMapping("/visit/source")
    @Operation(summary ="到访-渠道汇总")
    public R<SalesCountRespVO> getVisitSourceCount(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
    		) {
    	
    	return R.ok(salesCountService.getVisitSourceCount(startDate, endDate, deptIds));
    }
    
    @GetMapping("/visit/source/data")
    @Operation(summary ="到访-渠道汇总-下钻")
    public R<IPage<BatchVisitRespVO>> getVisitSourceData(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
    		@Schema(description = "id")@RequestParam(value = "id", required = true)String id,
    		@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
    		@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
    		@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
    		) {
    	
    	return R.ok(salesCountService.getVisitSourceData(startDate, endDate, deptIds,id,pageNum,pageSize,conditions));
    }
    
    @PostMapping("/visit/source/data/export")
    @Operation(summary ="到访-渠道汇总-下钻-导出")
    public void getVisitSourceDataExport(
    		HttpServletResponse response,
    		@RequestBody @Valid SalesDataExportReqVO vo
    		) throws IOException {
    	
    	List<VisitUserExcelDTO> list = salesCountService.getVisitSourceDataExport(
    			vo.getStartDate(), vo.getEndDate(), vo.getDeptIds(),
    			vo.getId(),vo.getPageNum(),vo.getPageSize(),
    			vo.getConditions());
    	exportActivityExcel(response,list,ClueUserExcelDTO.class,"渠道销售汇总-来访登记列表","来访登记数据");
    }
    
    @GetMapping("/sign/user")
    @Operation(summary ="首签-业务员汇总")
    public R<SalesCountRespVO> getSignUserCount(
    		@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
    		@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
    		@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
    		) {
    	
    	return R.ok(salesCountService.getSignUserCount(startDate, endDate, deptIds));
    }
    
	@GetMapping("/sign/user/data")
	@Operation(summary ="首签-业务员汇总-下钻")
	public R<IPage<SignUserRespVo>> getSignUserData(
			@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
			@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
			@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
			@Schema(description = "id")@RequestParam(value = "id", required = true)String id,
			@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
			@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
			@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
			) {
		
		return R.ok(salesCountService.getSignUserData(startDate, endDate, deptIds,id,pageNum,pageSize,conditions));
	}
	
	@PostMapping("/sign/user/data/export")
	@Operation(summary ="首签-业务员汇总-下钻-导出")
	public void getSignUserDataExport(
			HttpServletResponse response,
			@RequestBody @Valid SalesDataExportReqVO vo
			) throws IOException {
		
		List<SignUserExcelDTO> list = salesCountService.getSignUserDataExport(
				vo.getStartDate(), vo.getEndDate(), vo.getDeptIds(),
				vo.getId(),vo.getPageNum(),vo.getPageSize(),
				vo.getConditions());
    	exportActivityExcel(response,list,SignUserExcelDTO.class,"业务员销售汇总-首次签约列表","首次签约数据");
	}
	
	@GetMapping("/sign/source")
	@Operation(summary ="首签-渠道汇总")
	public R<SalesCountRespVO> getSignSourceCount(
			@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
			@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
			@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
			) {
		
		return R.ok(salesCountService.getSignSourceCount(startDate, endDate, deptIds));
	}
	
	@GetMapping("/sign/source/data")
		@Operation(summary ="首签-渠道汇总-下钻")
		public R<IPage<SignUserRespVo>> getSignSourceData(
				@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
				@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
				@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
				@Schema(description = "id")@RequestParam(value = "id", required = true)String id,
				@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
				@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
				@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
				) {
			
			return R.ok(salesCountService.getSignSourceData(startDate, endDate, deptIds,id,pageNum,pageSize,conditions));
		}
	
	@PostMapping("/sign/source/data/export")
	@Operation(summary ="首签-渠道汇总-下钻-导出")
	public void getSignSourceDataExport(
			HttpServletResponse response,
			@RequestBody @Valid SalesDataExportReqVO vo
			) throws IOException {
		
		List<SignSourceExcelDTO> list = salesCountService.getSignSourceDataExport(
				vo.getStartDate(), vo.getEndDate(), vo.getDeptIds(),
				vo.getId(),vo.getPageNum(),vo.getPageSize(),
				vo.getConditions());
    	exportActivityExcel(response,list,SignUserExcelDTO.class,"渠道销售汇总-首次签约列表","首次签约数据");
	}
	
	@GetMapping("/user/details")
	@Operation(summary ="业务员销明细")
	public R<SaleUserDetailsRespVO> getUserDetails(
			@Schema(description = "开始日期（yyyy-MM）")@RequestParam(value = "startDate", required = false)String startDate,
			@Schema(description = "结束日期（yyyy-MM）")@RequestParam(value = "endDate", required = false)String endDate,
			@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
			) {
		
		return R.ok(salesCountService.getUserDetails(startDate, endDate, deptIds));
	}
	
	@PostMapping("/user/details/export")
	@Operation(summary ="业务员销明细-导出")
	public void getUserDetailsExport(
			HttpServletResponse response,
			@RequestBody @Valid SalesDataExportReqVO vo
			) throws IOException {
		SaleUserDetailsExportRespVO userDetailsExport = salesCountService.getUserDetailsExport(
				vo.getStartDate(), 
				vo.getEndDate(), 
				vo.getDeptIds());
		List<List<String>> heads = new ArrayList<>();
		for (String heard : userDetailsExport.getHeards()) {
			List<String> headList = new ArrayList<>();
			headList.add(heard);
			heads.add(headList);
		}
		exportExcel(response,heads,userDetailsExport.getDataList(),null,"业务员销售明细","业务员销售明细");
	}
	
    public void exportExcel(HttpServletResponse response, 
    		List<List<String>> heads,
    		List<List<Object>> dataList,
    		Class clazz,
    		String excelName,
    		String sheetName) throws IOException {
        // 1. 设置响应头（关键：避免中文乱码、指定文件类型）
    	response.addHeader("Pargam", "no-cache");
    	response.addHeader("Cache-Control", "no-cache");
    	response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        //response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // Excel 2007+格式
    	//response.setContentType("application/octet-stream;charset=UTF-8"); // Excel 2007+格式
        response.setCharacterEncoding("utf-8");
        // 文件名编码：使用URLEncoder避免中文乱码，替换“+”为“%20”（浏览器兼容）
        String fileName = URLEncoder.encode(excelName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName + ".xlsx");
		
		// 表头样式
        WriteCellStyle headCellStyle = new WriteCellStyle();
        WriteFont headFont = new WriteFont();
        headFont.setBold(true);
        headCellStyle.setWriteFont(headFont);
        headCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 内容样式
        WriteCellStyle contentCellStyle = new WriteCellStyle();
        contentCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(headCellStyle, contentCellStyle);
        
        AbstractMergeStrategy abstractMergeStrategy = new AbstractMergeStrategy(){
        	// 记录当前列（第0列）的合并起始行索引
            private int startRow = 0;
        
    	    @Override
    	    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
    	    	// 只处理第0列，且跳过表头行（表头行索引为0，数据行从1开始）
    	        if (cell.getColumnIndex() != 0 || relativeRowIndex == null) {
    	            return;
    	        }

    	        // 当前数据行的索引（relativeRowIndex是相对于表头的偏移量，0表示表头，1表示第一行数据）
    	        int currentRow = cell.getRowIndex();
    	        // 表头行不参与合并，从第一行数据开始处理（relativeRowIndex >= 1）
    	        if (relativeRowIndex < 1) {
    	            startRow = currentRow; // 初始化起始行为表头行下方第一行
    	            return;
    	        }

    	        // 获取上一行第0列的单元格
    	        Cell prevCell = sheet.getRow(currentRow - 1).getCell(0);
    	        if (prevCell == null) {
    	            return;
    	        }

    	        // 比较当前单元格与上一行单元格的值
    	        String currentValue = cell.getStringCellValue();
    	        String prevValue = prevCell.getStringCellValue();

    	        // 如果当前值与上一行不同，说明连续相同值结束，合并从startRow到currentRow-1的区域
    	        if (!currentValue.equals(prevValue)) {
    	            if (startRow < currentRow - 1) { // 只有超过1行时才合并
    	                sheet.addMergedRegion(new CellRangeAddress(
    	                    startRow,       // 合并起始行
    	                    currentRow - 1, // 合并结束行
    	                    0, 0            // 合并列（第0列）
    	                ));
    	            }
    	            startRow = currentRow; // 更新起始行为当前行
    	        }

    	        // 处理最后一行数据：如果是最后一行，且存在未合并的连续区域，执行合并
    	        if (relativeRowIndex == dataList.size() - 1) {
    	            if (startRow < currentRow) {
    	                sheet.addMergedRegion(new CellRangeAddress(
    	                    startRow,
    	                    currentRow,
    	                    0, 0
    	                ));
    	            }
    	        }
    	    }
    	    	
        };
        
        //设置单元格自适应
        CellWriteHandler cellWriteHandler = new CellWriteHandler () {
        	// 记录每列的最大内容宽度（字符数）
            private final Map<Integer, Integer> columnMaxWidth = new HashMap<>();

            @Override
            public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
            	        List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                // 跳过表头（如果需要表头也参与自适应，可移除此判断）
				/*if (isHead) {
				    return;
				}*/

                Sheet sheet = writeSheetHolder.getSheet();
                int columnIndex = cell.getColumnIndex();

                // 获取单元格内容（注意：需处理不同数据类型）
                String cellValue = getCellValue(cell);
                if (cellValue == null) {
                    return;
                }

                // 计算内容宽度（字符数，中文占2个字符，英文/数字占1个字符）
                int width = calculateCellWidth(cellValue);

                // 更新当前列的最大宽度
                if (columnMaxWidth.containsKey(columnIndex)) {
                    int currentMax = columnMaxWidth.get(columnIndex);
                    if (width > currentMax) {
                        columnMaxWidth.put(columnIndex, width);
                    }
                } else {
                    columnMaxWidth.put(columnIndex, width);
                }

                // 当所有数据写入完成后，设置列宽（监听最后一行的处理事件）
                if (cell.getRowIndex() == sheet.getLastRowNum()) {
                    setColumnWidth(sheet);
                }
            }

            /**
             * 获取单元格内容（适配不同数据类型）
             */
            private String getCellValue(Cell cell) {
                if (cell == null) {
                    return "";
                }
                switch (cell.getCellType()) {
                    case STRING:
                        return cell.getStringCellValue();
                    case NUMERIC:
                        return String.valueOf(cell.getNumericCellValue());
                    case BOOLEAN:
                        return String.valueOf(cell.getBooleanCellValue());
                    default:
                        return "";
                }
            }

            /**
             * 计算单元格内容宽度（中文占2字符，英文/数字占1字符）
             */
            private int calculateCellWidth(String value) {
                int width = 0;
                for (char c : value.toCharArray()) {
                    // 中文及全角字符占2个宽度，其他占1个
                    if (c > 127 || c == 9) {
                        width += 2;
                    } else {
                        width += 1;
                    }
                }
                // 预留2个字符的边距
                return width + 2;
            }

            /**
             * 设置列宽（POI的列宽单位是1/256个字符宽度）
             */
            private void setColumnWidth(Sheet sheet) {
                for (Map.Entry<Integer, Integer> entry : columnMaxWidth.entrySet()) {
                    int columnIndex = entry.getKey();
                    int width = entry.getValue();
                    // 转换为POI的列宽单位（宽度×256），并限制最大宽度避免过宽
                    int maxWidth = Math.min(width * 256, 200 * 256); // 最大200字符
                    sheet.setColumnWidth(columnIndex, maxWidth);
                }
            }
        };
        
     // 2. 导出配置
        ExcelWriter excelWriter = null;
        try {
        	excelWriter = EasyExcel.write(response.getOutputStream()).build();
		    
		    WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
	                .head(heads)
	                .registerWriteHandler(styleStrategy)
	                .registerWriteHandler(cellWriteHandler)
	                .registerWriteHandler(abstractMergeStrategy)
	                .build();
		    excelWriter.write(dataList, writeSheet);
        }finally {
            // 6. 关键：手动结束写入但不关闭响应流
            if (excelWriter != null) {
                excelWriter.finish(); // 刷新数据到输出流
            }
            // 注意：不要关闭 response.getOutputStream()！
        }
    }
	
	@GetMapping("/sign/user/rank")
	@Operation(summary ="业务员首签排名top10")
	public R<List<SalesDataRespVO>> getSignUserRank(
			@Schema(description = "开始日期（yyyy-MM）")@RequestParam(value = "startDate", required = false)String startDate,
			@Schema(description = "结束日期（yyyy-MM）")@RequestParam(value = "endDate", required = false)String endDate,
			@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds
			) {
		
		return R.ok(salesCountService.getSignUserRank(startDate, endDate, deptIds));
	}
	
	@GetMapping("/sign/user/rank/details")
	@Operation(summary ="业务员首签排名top10-下钻")
	public R<IPage<SignUserRespVo>> getSignUserRankDetails(
			@Schema(description = "开始日期（yyyy-MM-dd）")@RequestParam(value = "startDate", required = false)String startDate,
			@Schema(description = "结束日期（yyyy-MM-dd）")@RequestParam(value = "endDate", required = false)String endDate,
			@Schema(description = "deptIds")@RequestParam(value = "deptIds", required = false)List<String> deptIds,
			@Schema(description = "页码")@RequestParam(value = "pageNum", required = true,defaultValue="1") Integer pageNum,
			@Schema(description = "每页显示记录数")@RequestParam(value = "pageSize", required = true,defaultValue="10") Integer pageSize,
			@Schema(description = "条件(json格式)")@RequestParam(value = "conditions", required = false) String conditions
			) {
		
		return R.ok(salesCountService.getSignUserRankDetails(startDate, endDate, deptIds,pageNum,pageSize,conditions));
	}
	
	@PostMapping("/sign/user/rank/details/export")
	@Operation(summary ="业务员首签排名top10-下钻-导出")
	public void getSignUserRankDetailsExport(
			HttpServletResponse response,
			@RequestBody @Valid SalesDataExportReqVO vo
			) throws IOException {
		
		List<SignUserExcelDTO> list = salesCountService.getSignUserRankDetailsExport(
				vo.getStartDate(), vo.getEndDate(), vo.getDeptIds(),
				vo.getPageNum(),vo.getPageSize(),vo.getConditions());
    	exportActivityExcel(response,list,SignUserExcelDTO.class,"首次签约数据","首次签约数据");
	}
}
